﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Collections.ObjectModel;

namespace M3G {
    public class SkinnedMesh : Mesh {

        #region Field
        Node[]      bones;
        Transform[] restPoses;
        #endregion


        #region Method
        public SkinnedMesh (int numSubmeshes, int numMorphTargets, Group skeleton)
            : base (numSubmeshes, numMorphTargets) {
            if (skeleton == null) {
                throw new ArgumentNullException ("Skeleton is null");
            }
            if (skeleton.GetType () == typeof (World)) {
                throw new ArgumentException ("Skeleton node must not be World");
            }
            if (skeleton.Parent != null) {
                throw new ArgumentException ("Skeleton node must be \"parent is null\"");
            }

            skeleton.SetParent (this);
            this.children.Add (skeleton);
            this.bones = new Node[0];
            this.restPoses = new Transform[0];
        }

        protected SkinnedMesh (SkinnedMesh mesh)
            : base (mesh) {
            this.restPoses = (Transform[])mesh.restPoses.Clone ();
            this.bones = new Node[mesh.BoneCount];

            // ボーンの参照先を複製後のスケルトンに付け替え
            var meshSkeleton = mesh.Skeleton.Descendants;
            var thisSkeleton = this.Skeleton.Descendants;
            for (var i=0; i < mesh.BoneCount; i++) {
                var boneIndex = Array.IndexOf (meshSkeleton, mesh.bones[i]);
                this.bones[i] = (Group)thisSkeleton[boneIndex];
            }

        }

        public int BoneCount {
            get { return bones.Length; }
        }

        public Node[] Bones {
            get {
                return (Node[])bones.Clone ();
            }
        }

        public void SetBones (Node[] bones) {
            if (bones == null) {
                throw new ArgumentNullException ("Bones is null");
            }
            if (bones.Contains (null)) {
                throw new ArgumentNullException ("Some elements of Bones are null");
            }

            var boneCount = bones.Length;

            this.bones     = new Node[boneCount];
            this.restPoses = new Transform[boneCount];
            for (var i = 0; i < boneCount; i++) {
                this.bones[i] = bones[i];
                this.restPoses[i] = GetTransformTo(bones[i]);
            }
        }

        public Transform BoneTransform (Node bone) {
            if (bone == null) {
                throw new ArgumentNullException ("Bone is null");
            }
            if (!Skeleton.Descendants.Contains (bone)) {
                throw new ArgumentException ("Bone is not in this skeleton group");
            }

            int boneIndex = Array.IndexOf (bones, bone);
            return (boneIndex ==-1) ? new Transform() : restPoses[boneIndex];
        }


        public Group Skeleton {
            get { return children[0] as Group; }
        }

        public override Object3D[] References {
            get {
                var q = new List<Object3D> ();
                q.AddRange (base.References);
                q.AddRange (bones);
                q.RemoveAll (x => x == null);
                return q.ToArray ();
            }
        }

        public override Object3D Duplicate () {
            var msh = new SkinnedMesh (this);
            msh.parent = null;
            return msh;
        }

        #endregion

    }
}
